#include <signal.h>
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

#include "uthread.h"
#include "linked.h"
#include "queue.h"
#include "util.h"

#ifndef STACK_SIZE
#define STACK_SIZE 1048576
#endif

#ifndef PRIORITIES
#define PRIORITIES 10
#endif


#define MIN_PRIORITY (PRIORITIES-1)


#ifndef TIME_SLICE_USEC
#define TIME_SLICE_USEC 1000
#endif

#define TEST

#ifdef TEST
#define TIMER_TYPE ITIMER_REAL
#define TIMER_SIG SIGALRM
#else
#define TIMER_TYPE ITIMER_VIRUAL
#define TIMER_SIG SIGVTALRM
#endif


static volatile int ID = 0;
static queue_t *queues[PRIORITIES];





//obsolete
/*static queue_t *queue;*/

static linked_t* current_thread; 
static queue_t *deleted; 


void set_timer(int usec)
{
    struct itimerval timer;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 0;
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = usec;
    setitimer(TIMER_TYPE, &timer, NULL);
}

void reset_timer(){
    set_timer(TIME_SLICE_USEC);
}

void auto_yield(int sig, siginfo_t* info, void* v);

void ignore_signals()
{
    signal(TIMER_SIG, SIG_IGN);
}

void restore_signals()
{
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = (void*)auto_yield;
    sigaction(TIMER_SIG, &sa, NULL);
}

void make_stack(ucontext_t* context)
{
    char* stack = malloc(STACK_SIZE);
    CHECK_PTR(stack);
    CHECK_ARG(context);
    context->uc_stack.ss_flags = 0;
    context->uc_stack.ss_sp = stack;

    context->uc_stack.ss_size = sizeof stack;
}

ucontext_t *acquire_context()
{
    ucontext_t* context = malloc(sizeof(ucontext_t));
    CHECK_PTR(context);
    make_stack(context);
    if(getcontext(context)==-1)
    {
        die("failed to getcontext in acquire_context()");
    }
    context->uc_link = NULL;
    if(context == NULL)
    {
        die("Failed to acquire context.");
    }
    return context; 
}

linked_t* find_link()
{
    int i; 
    for(i = 0; i < PRIORITIES; i++)
    {
        if(!queue_is_empty(queues[i]))
        {
            linked_t *node = queues[i]->current;
            return node;
        }
    }
    die("No running threads found");
}

linked_t* get_higher_link(int priority)
{
    int i;
    for (i = 0; i < priority+1; i++)
    {
        if (!queue_is_empty(queues[i]))
        {
            return queues[i]->current;
        }
    }
    return NULL;
}

linked_t* pop_link()
{
    int i; 
    for(i = 0; i < PRIORITIES; i++)
    {
        if(!queue_is_empty(queues[i]))
        {
            return queue_pop(queues[i]);
        }
    }
    die("No running threads found");
}

bool run_t_exists(){
    int i; 
    for(i = 0; i<PRIORITIES; i++)
    {
        if(!queue_is_empty(queues[i]))
        {
            return true; 
        }
    }
    return false;
} 

int uthread_create(void (*func)(int), int val, int priority)
{
    ucontext_t* context = acquire_context();
    queue_reset_links(queues[priority], &get_higher_link); 

    //context->uc_link = link;
    makecontext(context, (void (*)(void)) func, 1, val);
    queue_push_context(queues[priority], context, priority);
    return ID++;
}

ucontext_t* deleter_context;
linked_t* swap_node = NULL;
void delete_func()
{
    // This function never returns, but always gives any remaining threads a 
    // chance to run, so it isnt a problem.
    while(true)
    {
        
        if (!queue_is_empty(deleted))
        {
            linked_t* delnode = queue_pop(deleted);
            //printf("We are deleting (%d) from (%d)\n", delnode->id, swap_node->id);
            if (delnode->context == NULL)
            {
                die("null context\n");
            }
            //delnode->context->uc_stack.ss_sp;
            //free(delnode->context->uc_stack.ss_sp);
            
            free(delnode->context);
            free(delnode);
            
        }
        
        swapcontext(deleter_context, swap_node->context);
    }
    
    
}

void create_deleter()
{
    deleter_context = acquire_context();
    makecontext(deleter_context, &delete_func, 0); 
}

void create_queues()
{
    int i;
    for (i = 0; i < PRIORITIES; i += 1)
    {
        queues[i] = queue_create();
    }
}

void uthread_init()
{
    create_queues();
    deleted = queue_create();
    ucontext_t *root_context = acquire_context();
    
    queue_push_context(queues[MIN_PRIORITY], root_context, MIN_PRIORITY);
    linked_t* main_thread = queues[MIN_PRIORITY]->current;
    current_thread = main_thread; 
    
    create_deleter();
    restore_signals(); 
    reset_timer();
}

void yield(bool manual){  
    
    if(!run_t_exists())
    {
        die("Yielded from nonexistent thread.");
    }
    
    linked_t *rmHead = current_thread;
    
    queue_insert(queues[rmHead->priority], rmHead);
    
    linked_t *newHead = find_link(); 
    current_thread = newHead;
    
    if(newHead->context == NULL)
    {
        die("Yielded from null context\n");
    }
    else if(newHead->id != rmHead->id)
    {
        if(!manual)
        {
            reset_timer();
        } 
        swapcontext(rmHead->context, newHead->context);
    }
}

void auto_yield(int sig, siginfo_t* info, void* v)
{
    ucontext_t* context = (ucontext_t*)v;
    yield(false); 
    
}
    
void uthread_yield()
{
    yield(true);
}

void uthread_exit()
{
    if(!run_t_exists())
    {
        die("Thread exited when no running thread remained.\n");
    }
    
    linked_t *rmHead = pop_link();
    swap_node = rmHead;
    swapcontext(rmHead->context, deleter_context);
    
    queue_insert(deleted, rmHead);
    linked_t* next = find_link();
    rmHead->context->uc_link = next->context;
    if(!run_t_exists())
    {
        exit(0);
    }
}

void uthread_mutex_init(uthread_mutex_t *lockVar)
{
    lockVar->queue = queue_create(); 
}

void uthread_mutex_lock(uthread_mutex_t *lockVar)
{
    
    ignore_signals(); 
    if(queue_is_empty(lockVar->queue))
    {
        // We already have the lock. Proceed immediately to the critical section
    }
    else
    {
        linked_t *current = pop_link();
        queue_insert(lockVar->queue, current);
        queue_reset_links(queues[current->priority], &get_higher_link); 
    }
    restore_signals(); 
}
    
void uthread_mutex_unlock(uthread_mutex_t *lockVar)
{
    ignore_signals(); 
    if(queue_is_empty(lockVar->queue))
    {
        //There are no threads to wake up
    }
    else
    {
        linked_t *current = queue_pop(lockVar->queue);
        queue_insert(queues[current->priority], current); 
        queue_reset_links(queues[current->priority],&get_higher_link); 
    }
    restore_signals(); 
}













